Passed
Pull Request — master (#16)
by
unknown
03:09
created

DrawHelpers.ts ➔ setKeyboardDependencyHighlightHandler   B

Complexity

Conditions 6

Size

Total Lines 40
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 33
dl 0
loc 40
rs 8.1546
c 0
b 0
f 0
cc 6
1
import { changeZoom, getLabelTextDimensions, getNodeDimensions, selectHighLightedNodes } from './GraphHelpers';
2
import { DependencyLink, DependencyNode, NodeSelection } from '../../components/types';
3
import { forceCenter, forceCollide, forceLink, forceSimulation, forceY } from 'd3-force';
4
import { event, select, Selection } from 'd3-selection';
5
import { zoom } from 'd3-zoom';
6
import { BASE_FONT_SIZE, ElementColors, LabelColors, MAXIMUM_ZOOM_SCALE, MINIMUM_ZOOM_SCALE, TextColors } from '../AppConsts';
7
8
export function createLinkElements(zoomLayer: NodeSelection<SVGGElement>, links: DependencyLink[]) {
9
    return zoomLayer
10
        .append('g')
11
        .attr('id', 'links')
12
        .lower()
13
        .selectAll<HTMLElement, DependencyLink>('line.link')
14
        .data(links)
15
        .enter()
16
        .append<SVGPathElement>('svg:path')
17
        .attr('class', 'link')
18
        .attr('marker-end', 'url(#provider)')
19
        .style('stroke-width', 1);
20
}
21
22
export function createLabels(labelNodesGroup: NodeSelection<SVGGElement>, nodes: DependencyNode[]) {
23
    labelNodesGroup
24
        .selectAll('g')
25
        .data(nodes)
26
        .append<SVGPathElement>('svg:path')
27
        .attr('class', 'label')
28
        .attr('fill', LabelColors.DEFAULT)
29
        .attr('d', createLabelPath);
30
}
31
32
export function createTextElements(labelNodesGroup: NodeSelection<SVGGElement>, nodes: DependencyNode[]) {
33
    return labelNodesGroup
34
        .selectAll<HTMLElement, DependencyNode>('g#labels')
35
        .data(nodes)
36
        .enter()
37
        .append<SVGGElement>('g')
38
        .attr('cursor', 'pointer')
39
        .append('text')
40
        .attr('font-size', BASE_FONT_SIZE)
41
        .attr('fill', TextColors.DEFAULT)
42
        .text(d => d.name);
43
}
44
45
export function createMarkers(svgContainer: NodeSelection<SVGSVGElement>): void {
46
    svgContainer
47
        .append('svg:defs')
48
        .append('svg:marker')
49
        .attr('id', 'provider')
50
        .attr('viewBox', '-5 -5 40 10')
51
        .attr('refX', 15)
52
        .attr('refY', 0)
53
        .attr('markerWidth', 40)
54
        .attr('markerHeight', 40)
55
        .attr('orient', 'auto')
56
        .append('svg:path')
57
        .attr('d', 'M0,-5L20,0L0,5,q10 -5,0 -10')
58
        .attr('fill', '#dcdee0');
59
}
60
61
export function createSimulation(nodes: DependencyNode[], links: DependencyLink[], width: number, height: number) {
62
    return forceSimulation(nodes)
63
        .force(
64
            'dependency',
65
            forceLink<DependencyNode, DependencyLink>(links)
66
                .distance(180)
67
                .id((node: DependencyNode) => node.name)
68
        )
69
        .force('center', forceCenter(width / 2, height / 2))
70
        .force('y', forceY(0.5))
71
        .force('collide', forceCollide(140))
72
        .force('nodeCollide', forceCollide(140));
73
}
74
75
export function createSVGContainer(
76
    container: HTMLDivElement,
77
    width: number,
78
    height: number
79
): Selection<SVGSVGElement, DependencyNode, Element, HTMLElement> {
80
    return select<Element, DependencyNode>(`#${container.id}`)
81
        .append('svg')
82
        .attr('id', 'container')
83
        .attr('preserveAspectRatio', 'xMidYMid meet')
84
        .attr('viewBox', `0 0 ${width} ${height}`)
85
        .attr('width', width)
86
        .attr('height', height);
87
}
88
89
export function createZoom(svgContainer: NodeSelection<SVGSVGElement>): Selection<SVGGElement, DependencyNode, Element, HTMLElement> {
90
    const zoomLayer = svgContainer.append('g').attr('id', 'zoom');
91
92
    svgContainer
93
        .call(
94
            zoom<SVGSVGElement, DependencyNode>()
95
                .scaleExtent([MINIMUM_ZOOM_SCALE, MAXIMUM_ZOOM_SCALE])
96
                .on('zoom', changeZoom)
97
        )
98
        .on('dblclick.zoom', null);
99
100
    return zoomLayer;
101
}
102
103
export function createLinkPath(this: Element, link: DependencyLink): void {
104
    if (!link.source.x || !link.source.y || !link.target.x || !link.target.y) {
105
        return;
106
    }
107
108
    const xDiff = link.source.x - link.target.x;
109
    const yDiff = link.source.y - link.target.y;
110
111
    const isSourceOnTheLeft = xDiff < 0;
112
    const isSourceBelowTarget = yDiff > 0;
113
114
    const angleInRadians = Math.abs(Math.atan(yDiff / xDiff));
115
    const cosinus = Math.cos(angleInRadians);
116
    const sinus = Math.sin(angleInRadians);
117
118
    const offsetXLeft = -50 * cosinus;
119
    const offsetY = 50 * sinus;
120
    const offsetYBelow = -offsetY - 5;
121
122
    const sourceLabelWidth = getNodeDimensions(link.source).width;
123
    link.source.width = sourceLabelWidth;
124
    const targetLabelWidth = getNodeDimensions(link.target).width;
125
    link.target.width = targetLabelWidth;
126
127
    const sourceNewX = isSourceOnTheLeft ? (sourceLabelWidth + 20) * cosinus : offsetXLeft;
128
    const sourceNewY = isSourceBelowTarget ? offsetYBelow : offsetY;
129
130
    const targetNewX = isSourceOnTheLeft ? offsetXLeft : (targetLabelWidth + 20) * cosinus;
131
    const targetNewY = isSourceBelowTarget ? offsetY : offsetYBelow;
132
133
    select<Element, DependencyLink>(this)
134
        .attr(
135
            'd',
136
            'M' +
137
                (link.source.x + sourceNewX) +
138
                ',' +
139
                (link.source.y + sourceNewY) +
140
                ', ' +
141
                (link.target.x + targetNewX) +
142
                ',' +
143
                (link.target.y + targetNewY)
144
        )
145
        .attr('stroke', LabelColors.DEFAULT);
146
}
147
148
export function createLabelPath(this: Node, node: DependencyNode) {
149
    const labelTextDimensions = getLabelTextDimensions(this);
150
151
    if (!labelTextDimensions) {
152
        return '';
153
    }
154
155
    const labelTextWidth = labelTextDimensions.width;
156
157
    const { isConsumer, isProvider } = node;
158
159
    if (isConsumer && isProvider) {
160
        return 'M4.5,35l9.37,14.59L4.5,64.18h' + (labelTextWidth + 45) + 'l9-14.59L' + (labelTextWidth + 49.5) + ',35H4.5z';
161
    }
162
163
    if (isProvider) {
164
        return 'M' + (labelTextWidth + 49.5) + ',35H4.5l9.37,14.59L4.5,64.18h' + (labelTextWidth + 45);
165
    }
166
167
    if (isConsumer) {
168
        return 'M4.5,64.18h' + (labelTextWidth + 45) + 'l9.42-14.59L' + (labelTextWidth + 49.5) + ',35H4.5';
169
    }
170
171
    return 'M4.5,64.18h' + (labelTextWidth + 55) + 'L' + (labelTextWidth + 59.5) + ',35H4.5';
172
}
173
174
export function createHighlightBackground(
175
    svgContainer: NodeSelection<SVGGElement>
176
): Selection<SVGRectElement, DependencyNode, Element, HTMLElement> {
177
    return svgContainer
178
        .append('rect')
179
        .attr('id', 'highlight-background')
180
        .attr('width', 0)
181
        .attr('height', 0)
182
        .attr('x', 0)
183
        .attr('y', 0)
184
        .attr('rx', 5)
185
        .attr('ry', 5)
186
        .attr('fill', ElementColors.HIGHLIGHT_BACKGROUND)
187
        .style('opacity', 0);
188
}
189
190
export function createDetailsButton(svgContainer: NodeSelection<SVGGElement>) {
191
    const detailsButtonWrapper = svgContainer
192
        .append('g')
193
        .attr('id', 'details-button')
194
        .on('click', () => {
195
            if (selectHighLightedNodes().data().length) {
196
                event.stopPropagation();
197
            }
198
        })
199
        .attr('cursor', 'pointer');
200
    detailsButtonWrapper
201
        .append('rect')
202
        .style('opacity', 0)
203
        .attr('fill', ElementColors.BUTTON);
204
    detailsButtonWrapper
205
        .append('text')
206
        .style('opacity', 0)
207
        .style('text-anchor', 'middle')
208
        .attr('fill', TextColors.HIGHLIGHTED)
209
        .text('Details');
210
    return detailsButtonWrapper;
211
}
212